package org.teamh.gui.engine.fbo;

//import static org.lwjgl.opengl.EXTFramebufferObject.*;
//import static org.lwjgl.opengl.EXTFramebufferMultisample.*;
//import static org.lwjgl.opengl.EXTFramebufferBlit.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL14.*;
import static org.lwjgl.opengl.GL30.*;

import java.nio.FloatBuffer;
import java.util.HashMap;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.GLContext;


public class FBORenderer {

	private static HashMap<String, FBORenderer> renderers = new HashMap<String, FBORenderer>();
	
	private int texID;
	private int depthTexID;
	
	private int width;
	private int height;
	private int fbo;
	private int multisampleFBO;
	private int sampling = 4;
	
	private boolean multisampling = false;
	private boolean enableMultisampling;
	
	private FBORenderer(int width, int height, boolean enableMultisampling, String type) {
		this.width = width;
		this.height = height;
		this.enableMultisampling = enableMultisampling;
		
		init(type);
	}
	
	private void init(String type) {
		if(isEnableMultisampling()) {
			multisampling = isMultisampling();
		}
				
		fbo = glGenFramebuffers();

		texID = glGenTextures();

		depthTexID = glGenTextures();

		glBindTexture(GL_TEXTURE_2D, depthTexID);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		
		//On some systems, this can cause the system to slow down at startup. In my case, the onboard Intel GPU caused this problem. 
		FloatBuffer depthBuffer = BufferUtils.createFloatBuffer(width * height);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depthBuffer);
		
		glBindTexture(GL_TEXTURE_2D, texID);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		FloatBuffer textureBuffer = BufferUtils.createFloatBuffer(width * height * 4);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_FLOAT, textureBuffer);

		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0);
		
		Check.fboError(type);
		
		if(multisampling) {
			sampling = Math.min(GL_MAX_SAMPLES, sampling);
			
			glBindFramebuffer(GL_FRAMEBUFFER, 0);
			
			// multi sampled color buffer
			int colorBufferID = glGenRenderbuffers();
			glBindRenderbuffer(GL_RENDERBUFFER, colorBufferID);
			glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampling, GL_RGBA, width, height);

			Check.fboError(type + "(color buffer): ");
			
			// multi sampled depth buffer
			int depthBufferID = glGenRenderbuffers();
			glBindRenderbuffer(GL_RENDERBUFFER, depthBufferID);
			glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampling, GL_DEPTH_COMPONENT, width, height);

			Check.fboError(type + "(depth buffer): ");
			
			// unbind
			glBindRenderbuffer( GL_RENDERBUFFER, 0);

			// create fbo for multi sampled content and attach buffers
			multisampleFBO = glGenFramebuffers();
			glBindFramebuffer(GL_FRAMEBUFFER, multisampleFBO);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBufferID);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferID);

			Check.fboError(type + "(multisample fbo): ");
		}

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}
	
	public void preRender() {
		if(multisampling) {
			glBindFramebuffer(GL_FRAMEBUFFER, multisampleFBO);
		} else {
			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		}
		
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}
	
	public void postRender() {
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		
		if(multisampling) {
			glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampleFBO);
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
			glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
			glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
		}
	}
	
	public int getDepthTexID() {
		return depthTexID;
	}
	
	public int getTexID() {
		return texID;
	}
	
	public int getWidth() {
		return width;
	}
	
	public int getHeight() {
		return height;
	}
	
	private boolean isMultisampling() {
		ContextCapabilities c = GLContext.getCapabilities();
		return c.GL_EXT_framebuffer_blit && c.GL_EXT_framebuffer_multisample;
	}

	public boolean isEnableMultisampling() {
		return enableMultisampling;
	}
	
	public static FBORenderer getRenderer(String type, int width, int height, boolean enableMultisampling) {
		if(!renderers.containsKey(type)) {
			renderers.put(type, new FBORenderer(width, height, enableMultisampling, type));
		}
		
		return renderers.get(type);
	}
}
